package com.zeny.express.framework.sso;

import java.sql.Date;
import java.util.Arrays;

import javax.crypto.Cipher;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.zeny.express.framework.shared.domain.Constants;
import com.zeny.express.framework.shared.encoder.AESEncoder;
import com.zeny.express.framework.shared.encoder.MD5Encoder;

public class TokenMarshal {

	/**
	 * 日志
	 */
	protected static final Logger LOGGER = LoggerFactory
			.getLogger(TokenMarshal.class);

	private static final String KEY = "ASDFGHJKLzxcvbn6"; // TODO

	private static final String DELIMITER = ",";

	private static final int SIGNATURE_LENGTH = 8;

	private Cipher cipherEncrypt;

	private Cipher cipherDecrypt;

	public TokenMarshal() {
		cipherEncrypt = AESEncoder.getEncryptCipher(KEY.getBytes());
		cipherDecrypt = AESEncoder.getDecryptCipher(KEY.getBytes());
	}

	/**
	 * Token 加码
	 * 
	 * @author zhuwei
	 * @date 2013年9月11日 上午9:04:13
	 * @param token
	 * @return
	 * @throws TokenException
	 */
	public String marshal(Token token) throws TokenException {

		try {
			// 原始数据
			byte[] plain = TokenMarshal.serialize(token).getBytes();

			// 加密数据
			byte[] encrypt = AESEncoder.encrypt(cipherEncrypt, plain);

			// 数据签名
			byte[] signature = MD5Encoder.encode(plain);
			
			// 最终数据
			byte[] data = new byte[encrypt.length + SIGNATURE_LENGTH];
			
			System.arraycopy(encrypt, 0, data, 0, encrypt.length);
			System.arraycopy(signature, 0, data, encrypt.length, SIGNATURE_LENGTH);

			// Token 字符串
			return new String(Base64.encodeBase64(data),
					Constants.DEFAULT_CHARSET);
			
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
			throw new TokenException(TokenException.SYSTEM_ERROR);
		}

	}

	/**
	 * Token 解码
	 * 
	 * @author zhuwei
	 * @date 2013年9月11日 上午9:14:50
	 * @param str
	 * @return
	 * @throws TokenException
	 */
	public Token unMarshal(String str) throws TokenException {

		// Token 字符串
		byte[] data = Base64.decodeBase64(str);
		
		// 加密数据
		byte[] encrypt = new byte[data.length - SIGNATURE_LENGTH];

		// 数据签名
		byte[] signature = new byte[SIGNATURE_LENGTH];
		
		System.arraycopy(data, 0, encrypt, 0, encrypt.length);
		System.arraycopy(data, encrypt.length, signature, 0, SIGNATURE_LENGTH);

		// 原始数据
		byte[] plain = AESEncoder.decrypt(cipherDecrypt, encrypt);

		// 数据验证
		byte[] valid_signature = new byte[SIGNATURE_LENGTH];
		System.arraycopy(MD5Encoder.encode(plain), 0, valid_signature, 0, SIGNATURE_LENGTH);

		// 签名验证
		if (!Arrays.equals(signature, valid_signature)) {
			throw new TokenException(TokenException.TOKEN_ERROR);
		}

		Token token = deserialize(new String(plain));

		return token;

	}

	/**
	 * Token 解码
	 * 
	 * @author zhuwei
	 * @date 2013年9月11日 上午9:14:50
	 * @param str
	 * @return
	 * @throws TokenException
	 */
	public Token unMarshal1(String str) throws TokenException {

		// 加密数据
		byte[] encrypted = Base64.decodeBase64(str.substring(0, str.length()
				- SIGNATURE_LENGTH));

		// 原始数据
		byte[] plain = AESEncoder.decrypt(cipherDecrypt, encrypted);

		// 数据验证
		String signature = MD5Encoder.digest(plain).substring(0,
				SIGNATURE_LENGTH);

		// 签名验证
		if (!signature.equals(str.substring(str.length() - SIGNATURE_LENGTH))) {
			throw new TokenException(TokenException.TOKEN_ERROR);
		}

		Token token = deserialize(new String(plain));

		return token;

	}

	/**
	 * Token 序列化
	 * 
	 * @author zhuwei
	 * @date 2013年9月11日 上午9:06:49
	 * @param token
	 * @return
	 */
	private static String serialize(Token token) {
		StringBuilder str = new StringBuilder();

		str.append(token.getAppId()).append(DELIMITER);
		str.append(token.getUserId()).append(DELIMITER);
		str.append(token.getCreateTime().getTime()).append(DELIMITER);
		str.append(token.getExpireTime().getTime());

		return str.toString();
	}

	/**
	 * Token 反序列化
	 * 
	 * @author zhuwei
	 * @date 2013年9月11日 上午9:07:06
	 * @param tokenId
	 * @return
	 */
	private static Token deserialize(String tokenId) {

		String[] array = tokenId.split(DELIMITER);

		Token token = new Token();
		token.setAppId(array[0]);
		token.setUserId(array[1]);

		long createT = Long.valueOf(array[2]);
		long expireT = Long.valueOf(array[3]);

		token.setCreateTime(new Date(createT));
		token.setExpireTime(new Date(expireT));

		return token;
	}

}
